﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using FootSteps.Data;

namespace FootSteps.Domain.Loggers.Window
{
    public class OpenWindowLogger : DG.Common.Patterns.MVVM.ViewModelBase
    {
        public bool Started
        {
            get { return _started; }
            set
            {
                if (value.Equals(_started)) return;
                _started = value;
                OnPropertyChanged("Started");
            }
        }

        public delegate void WindowChangeHandler(IEnumerable<OpenWindowArgs> e);
        public event WindowChangeHandler OnWindowChange;

        private Task _task;
        private CancellationTokenSource _cancellationToken;

        public void Start()
        {
            if (Started) return;
            Started = true;
            _cancellationToken = new CancellationTokenSource();
            _task = Task.Factory.StartNew(() =>
            {
                while (true)
                {
                    if (_cancellationToken.IsCancellationRequested)
                    {
                        return;
                    }

                    DoWrok();

                    Thread.Sleep(100);
                }
            },_cancellationToken.Token);
        }
        public void Stop()
        {
            if (!Started) return;
            Started = false;

            _cancellationToken.Cancel(false);
        }
        
        private IDictionary<int, OpenWindowTitle> _lastState;
        private bool _started;

        private void DoWrok()
        {
            var currentState = GetOpenWindows();
            var args = new List<OpenWindowArgs>();
            var now = DateTime.Now;

            if (_lastState == null)
            {
                args.AddRange(currentState.Select(c => new OpenWindowArgs(c.Value, now, WindowState.Opened)));
            }
            else
            {
                var removedProcess = _lastState.Where(s => !currentState.ContainsKey(s.Key)).ToArray();
                args.AddRange(removedProcess.Select(p => new OpenWindowArgs(p.Value, now, WindowState.Closed)));

                foreach (var state in currentState)
                {
                    if (!_lastState.ContainsKey(state.Key))
                    {
                        args.Add(new OpenWindowArgs(state.Value, now, WindowState.Opened));
                    }
                    else if (state.Value.Title != _lastState[state.Key].Title)
                    {
                        args.Add(new OpenWindowArgs(state.Value, now, WindowState.TitleChanged));
                    }
                }
            }
            _lastState = currentState;
            if (args.Any()) RaiseProcessChange(args.ToArray());
        }

        private void RaiseProcessChange(IEnumerable<OpenWindowArgs> args)
        {
            if (OnWindowChange != null)
                OnWindowChange(args);
        }
        
        public static IDictionary<int, OpenWindowTitle> GetOpenWindows()
        {
            var shellWindow = GetShellWindow();
            var windows = new Dictionary<int, OpenWindowTitle>();

            EnumWindows(delegate(IntPtr hWnd, int lParam)
            {
                if (hWnd == shellWindow) return true;
                if (!IsWindowVisible(hWnd)) return true;

                var length = GetWindowTextLength(hWnd);
                if (length == 0) return true;

                var builder = new StringBuilder(length);
                GetWindowText(hWnd, builder, length + 1);

                var title = builder.ToString();
                var pid = GetProcessIdFromWindowHandle(hWnd);

                var process = Process.GetProcessById(pid);
                windows[pid] = new OpenWindowTitle(hWnd, title, process);

                return true;
            }, 0);

            return windows;
        }

        private static int GetProcessIdFromWindowHandle(IntPtr hWnd)
        {
            uint id;
            GetWindowThreadProcessId(hWnd, out id);
            return (int) id;
        }

        delegate bool EnumWindowsProc(IntPtr hWnd, int lParam);

        
        #region P/Invokes
        [DllImport("USER32.DLL")]
        static extern bool EnumWindows(EnumWindowsProc enumFunc, int lParam);

        [DllImport("USER32.DLL")]
        private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

        [DllImport("USER32.DLL")]
        private static extern int GetWindowTextLength(IntPtr hWnd);

        [DllImport("USER32.DLL")]
        private static extern bool IsWindowVisible(IntPtr hWnd);

        [DllImport("USER32.DLL")]
        private static extern IntPtr GetShellWindow();

        [DllImport("user32.dll", SetLastError = true)]
        private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);

        #endregion

    }
}